﻿using System;
using System.Collections.Generic;
using System.Linq;
using RaytracerLib.MathLib;
using TomanuExtensions;
using YAXLib;
using RaytracerLib.Materials;

namespace RaytracerLib.Layers
{
    public abstract class Layer : IMaterialPart
    {
        private Layer m_parent;

        protected Layer()
        {
        }

        public Layer Parent
        {
            get
            {
                return m_parent;
            }
            internal set
            {
                m_parent = value;
            }
        }

        public override bool Equals(object a_obj)
        {
            if (Object.ReferenceEquals(a_obj, this))
                return true;

            if (a_obj == null)
                return false;
            Layer layer = a_obj as Layer;
            if ((object)layer == null)
                return false;

            return YAXSerializer.Compare(this, layer);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public virtual IEnumerable<Layer> Children
        {
            get
            {
                yield break;
            }
        }

        #region Operations

        public static void SetScale(Layer a_layer, Vector2 a_ratio)
        {
            for (; ; )
            {
                if (a_layer.GetType().IsDerivedFrom(typeof(ScaleOperationLayer)))
                {
                    (a_layer as ScaleOperationLayer).Ratio = a_ratio;
                    return;
                }

                if (a_layer.Children.Count() == 1)
                {
                    a_layer = (Layer)a_layer.Children.First();
                }
                else
                    break;
            }

            throw new InvalidOperationException();
        }

        public static IList<ArrayBase> GetAllArrays(Layer a_layer)
        {
            return (from leaf in GetLeaves(a_layer)
                    where leaf.GetType().IsDerivedFrom(typeof(ArrayBase))
                    select (ArrayBase)leaf).ToList();
        }

        public static IList<ProceduralLayer> GetAllScripts(Layer a_layer)
        {
            return (from leaf in GetLeaves(a_layer)
                    where leaf.GetType().IsDerivedFrom(typeof(ProceduralLayer))
                    select (ProceduralLayer)leaf).ToList();
        }

        public static IList<Layer> GetLeaves(Layer a_layer)
        {
            var children = a_layer.Children;
            if (children.Count() == 0)
                return new List<Layer> { a_layer };
            else
            {
                List<Layer> result = new List<Layer>();
                foreach (var child in children)
                    result.AddRange(GetLeaves(child));
                return result;
            }
        }

        public static void InsertBefore(Material a_material, Layer a_node, Layer a_new)
        {
            if (a_node.Parent == null)
            {
                if (!a_material.MaterialParts.Contains(a_node))
                    throw new InvalidOperationException();

                a_material.ReplaceMaterialPart(a_node, a_new);

                InsertAfter(a_new, a_node);
            }
            else
                InsertAfter(a_node.Parent, a_new);
        }

        private static Layer GetChild(Layer a_layer)
        {
            UnaryLayer ul = a_layer as UnaryLayer;

            if (ul == null)
                throw new NotImplementedException();

            return ul.ChildLayer;
        }

        private static void SetChild(Layer a_layer, Layer a_child)
        {
            UnaryLayer ul = a_layer as UnaryLayer;

            if (ul == null)
                throw new NotImplementedException();

            ul.ChildLayer = a_child;
        }

        public static void InsertAfter(Layer a_node, Layer a_new)
        {
            if (a_new.Children.Any(c => c != null))
            {
                if (Layer.GetChild(a_node) != null)
                    throw new NotImplementedException();

                Layer.SetChild(a_node, a_new);
            }
            else
            {
                Layer old = Layer.GetChild(a_node);
                Layer.SetChild(a_node, a_new);
                if (old != null)
                    Layer.SetChild(a_new, old);
            }
        }

        #endregion

        #region IMaterialPart

        public Tangents GetTangents(Intersection a_intersection)
        {
            Vector2 tx, ty;
            a_intersection.SceneObject.GetTangents(a_intersection, out tx, out ty);

            return GetTangents(a_intersection.UV, tx, ty, a_intersection);
        }

        public ColorFloat GetColor(Intersection a_intersection)
        {
            return GetColor(a_intersection.UV, a_intersection);
        }

        public double GetValue(Intersection a_intersection)
        {
            return GetValue(a_intersection.UV, a_intersection);
        }

        public virtual void RenderStart(RenderStartPhase a_phase)
        {
            foreach (var layer in Children)
                layer.RenderStart(a_phase);
        }

        public virtual void RenderEnd(bool a_all)
        {
            foreach (var layer in Children)
                layer.RenderEnd(a_all);
        }

        #endregion

        public abstract ColorFloat GetColor(Vector2 a_uv, Intersection a_intersection);

        public abstract double GetValue(Vector2 a_uv, Intersection a_intersection);

        public abstract Tangents GetTangents(Vector2 a_uv, Vector2 a_tx, Vector2 a_ty,
            Intersection a_intersection);
    }
}